// Mesh's shadow shader
var CSMEffectVertexShaderStr = "\
attribute vec3 inPosition;\
attribute float inObjIdx;\
uniform mat4 viewProjectionMat;\
uniform mat4 modelMat[25];\
void main(void)\
{\
  gl_Position = viewProjectionMat*modelMat[inObjIdx]*vec4(inPosition, 1.0);\
}";
var CSMEffectFragmentShaderStr = "\
void main(void)\
{\
  float depth = gl_FragCoord.z;\
  depth*= 16777216.0;\
  int d1 = floor(depth/65536.0);\
  int d2 = floor(mod(depth,65536.0)/256.0);\
  int d3 = floor(mod(depth,256.0));\
  float dx = float(d1)/256.0;\
  float dy = float(d2)/256.0;\
  float dz = float(d3)/256.0;\
  gl_FragColor = vec4(dx,dy,dz,1.0);\
}";
// AlphaRef's Shadow shader
var CSMEffectAlphaRefVertexShaderStr = "\
attribute vec3 inPosition;\
attribute float inObjIdx;\
attribute vec2 inTexCoord0;\
uniform mat4 viewProjectionMat;\
uniform mat4 modelMat[25];\
varying vec2 outTexcoord0;\
void main(void)\
{\
  gl_Position = viewProjectionMat*modelMat[inObjIdx]*vec4(inPosition, 1.0);\
  outTexcoord0 = inTexCoord0;\
}\
";
var CSMEffectAlphaRefFragmentShaderStr = "\
varying vec2 outTexcoord0;\
uniform sampler2D texture;\
uniform float alphaRef;\
void main(void)\
{\
  vec4 color = texture2D(texture,outTexcoord0);\
  if(color.a < alphaRef)\
    discard;\
  float depth = gl_FragCoord.z;\
  depth*= 16777216.0;\
  int d1 = floor(depth/65536.0);\
  int d2 = floor(mod(depth,65536.0)/256.0);\
  int d3 = floor(mod(depth,256.0));\
  float dx = float(d1)/256.0;\
  float dy = float(d2)/256.0;\
  float dz = float(d3)/256.0;\
  gl_FragColor = vec4(dx,dy,dz,1.0);\
}\
";

// Skinned Mesh's Shadow shader
var CSMEffectSkinnedMeshVertexShaderStr = "\
attribute vec3 inPosition;\
attribute vec4 inMatIdx;\
attribute vec4 inWeight;\
uniform mat4 modelMat;\
uniform mat4 viewProjectionMat;\
uniform mat4 boneMat[24];\
void main(void)\
{\
  vec4 netPosition;\
  mat4 combineMat = mat4(0.0);\
  float combineWeight=0.0;\
  for(int i=0;i<4;i++){\
    float idx = inMatIdx[i];\
    combineMat+= (boneMat[int(idx)]*inWeight[i]);\
    combineWeight+=inWeight[i];\
  }\
  netPosition = combineMat*vec4(inPosition, 1.0)/combineWeight;\
  gl_Position = viewProjectionMat*modelMat*netPosition;\
}\
";
function gewCSMEffect(scene3d)
{
  // General properties
  this.game = scene3d.game;
  this.gl = scene3d.gl;
  var gl = scene3d.gl;
  this.name = "CSMEffect";
  this.scene3d = scene3d;//Current scene3d that using this effect 
  this.materialList = {};
  this.currentPass = null;
  this.shaders = {};
  this.shaders.mesh = {};
  this.shaders.alphaRef = {};
  this.shaders.skinnedMesh = {};
  // Shadow map properties
  this.shadowMapWidth = scene3d.shadowMapWidth;
  this.shadowMapHeight = scene3d.shadowMapHeight;
  // Init Shader properties
  
  // Mesh's shadow shader
  var shader = this.shaders.mesh;
  shader.vs = null; shader.fs = null; shader.program = null;
  createGLSLShader(gl,shader,CSMEffectVertexShaderStr,CSMEffectFragmentShaderStr);
  
  // AlphaRef's shadow shader
  var shader = this.shaders.alphaRef;
  shader.vs = null; shader.fs = null; shader.program = null;
  createGLSLShader(gl,shader,CSMEffectAlphaRefVertexShaderStr,CSMEffectAlphaRefFragmentShaderStr);  
  
  // Skinned Mesh's shadow shader
  var shader = this.shaders.skinnedMesh;
  shader.vs = null; shader.fs = null; shader.program = null;
  createGLSLShader(gl,shader,CSMEffectSkinnedMeshVertexShaderStr,CSMEffectFragmentShaderStr);
  
  // Create fbo
  var frameBuffer = gl.createFramebuffer();
  var renderBuffer = gl.createRenderbuffer();
  var shadowMap = scene3d.shadowMap;
  gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);  
  gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuffer); 
  
  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.shadowMapWidth, this.shadowMapHeight);
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, shadowMap, 0);
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderBuffer);
  
  var re = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  if (re != gl.FRAMEBUFFER_COMPLETE) 
  {
    switch (re)
    {
      case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
        alert("gewCSMEffect: Error on create framebuffer : INCOMPLETE_ATTACHMENT");
        break;
      case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
        alert("gewCSMEffect: Error on create framebuffer : INCOMPLETE_DIMENSIONS");
        break;
      case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
        alert("gewCSMEffect: Error on create framebuffer : INCOMPLETE_MISSING_ATTACHMENT");
        break;
      case gl.FRAMEBUFFER_UNSUPPORTED:
        alert("gewCSMEffect: Error on create framebuffer : UNSUPPORTED");
        break;
      default:
        alert("gewCSMEffect: Error on create framebuffer : Unknown error!");
    }
  }
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  this.frameBuffer = frameBuffer;
  this.shadowMap = shadowMap;
  this.renderBuffer = renderBuffer;
  this.maxDistanceSquare = 80*80;
  this.mode = 0 ; // 0 : top-down , 1: 1st person
}

gewCSMEffect.prototype.destructor = function()
{
  var list  = this.materialList;
  var listLen = list.length;  
  for (var i in list)
  {
    list[i].destructor();
  }
  this.scene3d.removeEffect(this);
},  
gewCSMEffect.prototype.render = function(scene3d)
{
  var gl = this.gl;
  this.scene3d = scene3d;
  // Bind shaodow's fbo
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer);
  gl.viewport(0, 0, this.shadowMapWidth, this.shadowMapHeight);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  
  ////////////////////////////////////////////////////////////////////////////////
  // Find light's view& proj mat
  // Dectect camera mode
  var light = scene3d.mainLight;
  var lightDirection = light.getDirection();
  lightDirection.set(-lightDirection.x,-lightDirection.y,-lightDirection.z);
  var camera = scene3d.camera_system.getActiveCamera();
  var look_at_point = camera.m_look_at_position;
  var view_pos = scene3d.viewPosition;
  var view_dir = camera.m_forward;
  var inverseViewMat = scene3d.inverseViewMat;
  var canvas_width= this.game.width;
  var canvas_height = this.game.height;
  var ray0 = getPickingRay(0,0,camera,inverseViewMat);
  var ray1 = getPickingRay(canvas_width,0,camera,inverseViewMat);
  var ray2 = getPickingRay(canvas_width,canvas_height,camera,inverseViewMat);
  var ray3 = getPickingRay(0,canvas_height,camera,inverseViewMat);  
  
  var heightMap = scene3d.heightMap;
  if(!heightMap)
  {
    heightMap = this.heightMap = this.game.resourceManager.getFloatMap("heightmap");
  }
  var p0 = ray0.colisionWithHeightMap(heightMap,40,10,1);
  var p1 = ray1.colisionWithHeightMap(heightMap,40,10,1);
  var p2 = ray2.colisionWithHeightMap(heightMap,40,10,1);
  var p3 = ray3.colisionWithHeightMap(heightMap,40,10,1);
  
  if(p0&&p1&&p2&&p3) // top-down view
  {
    this.mode = 0;
    var aabb = new gewAabb(p0,p1);
    aabb.addInternalPoint(p2);
    aabb.addInternalPoint(p3);
    var center = aabb.min.add(aabb.max).divScalar(2);
    
    var dis = center.sub(view_pos).getLength();
    
    var w = (aabb.max.z - aabb.min.z)/2;
    var h = (aabb.max.x - aabb.min.x)/2;
    var max_size = Math.max(w,h);
    
    max_size = dis;
    var projMat = makeOrtho(-max_size,max_size,-max_size,max_size,-200,200);
            
    var light_h = view_pos.y;
    var v0y = new gewVec3(0,1,0);
    var length = light_h/ ( v0y.dot(lightDirection));
    var light_pos = center.add(lightDirection.mulScalar(length));
    var up = lightDirection.cross( new gewVec3(0,0,1));
    up.normalize();
    var viewMat = makeLookAt(light_pos.x,light_pos.y,light_pos.z,
                            center.x,center.y,center.z,
                            -1,0,0);   
  }
  else
  {
    this.mode = 1;
    var maxd = Math.sqrt(this.maxDistanceSquare);
    var hd = maxd/2;
    var center = view_pos.add( view_dir.mulScalar(hd) );
    
    var length = 30;
    var light_pos = center.add(lightDirection.mulScalar(length));
    var viewMat = makeLookAt(light_pos.x,light_pos.y,light_pos.z,
                            center.x,center.y,center.z,
                            -1,0,0);                            
    var max_size = hd;
    var projMat = makeOrtho(-max_size,max_size,-max_size,max_size,-200,200);
  }
  var viewProjMat = projMat.mul(viewMat);
  light.viewMat.set(viewMat.data);
  light.projectionMat.set(projMat.data);
  var shadowMat = light.shadowMat;
  GEW_SHADOW_BIAS_MAT.mul(projMat, shadowMat);
  shadowMat.mul(viewMat,shadowMat);
  /////////////////////////////////////////////////////////////////////////
  // Pass 0 : Render mesh's shadow
  this.currentPass = GEW_SHADOW_MESH;
  var shader = this.shaders.mesh;
  gl.useProgram(shader.program);
  gl.enableVertexAttribArray(shader.inPosition);
  gl.enableVertexAttribArray(shader.inObjIdx);
  // set GL state
  gl.enable(gl.DEPTH_TEST);
  //gl.enable(gl.CULL_FACE);
  gl.uniformMatrix4fv(shader.viewProjectionMat, false, viewProjMat.data);
  var materialList = this.materialList;
  for (var i in materialList) 
  {
    materialList[i].render();
  }
  
  //gl.disableVertexAttribArray(shader.inPosition);
  gl.disableVertexAttribArray(shader.inObjIdx);
  //gl.disable(gl.CULL_FACE);
  /////////////////////////////////////////////////////////////////////////
  // Pass 1 : Render alphaRef's shadow
  this.currentPass = GEW_SHADOW_ALPHA_REF;
  var shader = this.shaders.alphaRef;
  gl.useProgram(shader.program);
  ///gl.enableVertexAttribArray(shader.inPosition);
  gl.enableVertexAttribArray(shader.inObjIdx);
  gl.enableVertexAttribArray(shader.inTexCoord0);
  
  gl.uniformMatrix4fv(shader.viewProjectionMat, false, viewProjMat.data);
  var materialList = this.materialList;
  for (var i in materialList) 
  {
    materialList[i].render();
  }
    
  //gl.disableVertexAttribArray(shader.inPosition);
  gl.disableVertexAttribArray(shader.inObjIdx);
  gl.disableVertexAttribArray(shader.inTexCoord0);
  //gl.enable(gl.CULL_FACE);
  
  /////////////////////////////////////////////////////////////////////////
  // Pass 2 : Render skinnedMesh's shadow
  this.currentPass = GEW_SHADOW_SKINNED_MESH;
  var shader = this.shaders.skinnedMesh;
  gl.useProgram(shader.program);
  //gl.enableVertexAttribArray(shader.inPosition);
  gl.enableVertexAttribArray(shader.inMatIdx);
  gl.enableVertexAttribArray(shader.inWeight);
  
  gl.uniformMatrix4fv(shader.viewProjectionMat, false, viewProjMat.data);
  var materialList = this.materialList;
  for (var i in materialList) 
  {
    materialList[i].render();
  }
  gl.disableVertexAttribArray(shader.inPosition);
  gl.disableVertexAttribArray(shader.inMatIdx);
  gl.disableVertexAttribArray(shader.inWeight);
  
  // Restore GL state
  gl.disable(gl.DEPTH_TEST);
  //gl.disable(gl.CULL_FACE);
  // Restore render target to main framebuffer
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.viewport(0, 0, this.game.width, this.game.height);
  gl.currentPass = null;
}
